#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "thread_list.h"
#include "user_mutex.h"
#include "user_threads.h"
#include "common.h"

struct umutex_struct {
        uthread_id owner_uthread_id; //ID of holding thread. -1 if unlocked
        thread_list *wait_queue; //lock queue
        unsigned int lock_count;
};

thread_list *mutex_list;

int is_mutex_locked(uthread_mutex_t m) {
	int retVal =  m->owner_uthread_id >= 0;
	if (retVal == 0) {
		assert(m->lock_count == 0);
	}
	return retVal;
}

int is_mutex_initialized(uthread_mutex_t m) {
	return thread_list_is_in(mutex_list,m);
}

int is_mutex_queue_empty(uthread_mutex_t m) {
	return thread_list_is_empty(m->wait_queue);
}

void sig_block(sigset_t *sigmask) {
	sigemptyset(sigmask);
	sigaddset(sigmask , SIGALRM);
	sigprocmask(SIG_BLOCK , sigmask, NULL);
}

void sig_unblock(sigset_t *sigmask) {
	sigprocmask(SIG_UNBLOCK , sigmask, NULL);
}

void uthread_mutex_init_general() {
	mutex_list = thread_list_create();
}

int uthread_mutex_init(uthread_mutex_t* mutex) {
	lock_alarm();
	if(!mutex){
		return MUTEX_FAILURE;
	}
	if (is_mutex_initialized(*mutex)){
		return MUTEX_FAILURE;
	}
	*mutex = (uthread_mutex_t)malloc(sizeof(struct umutex_struct));
	if (!(*mutex)){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	(*mutex)->owner_uthread_id = -1;
	(*mutex)->wait_queue = thread_list_create();
	(*mutex)->lock_count = 0;
	if (!thread_list_insert_head(mutex_list, (void*)*mutex)){
		thread_list_destroy((*mutex)->wait_queue);
		free(*mutex);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_destroy(uthread_mutex_t mutex) {
	if (!mutex){
		return MUTEX_INVALID;
	}
	lock_alarm();
	node* curr = thread_list_find(mutex_list, mutex);
	if (curr == NULL){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}

	if (is_mutex_locked(mutex)) {
		unlock_alarm();
		return MUTEX_LOCKED;
	}

	assert(thread_list_is_empty(mutex->wait_queue));
	thread_list_destroy(mutex->wait_queue);
	if (!thread_list_remove(mutex_list, mutex)) {
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	free(mutex);
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_lock(uthread_mutex_t mutex) {
	if (!mutex)
		return MUTEX_INVALID;
	lock_alarm();
//	write(1,"!\n",2);
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
//	write(1,"@\n",2);
	if (!is_mutex_locked(mutex)) {
		assert(mutex->lock_count == 0);
//		write(1,"#\n",2);
		assert(thread_list_is_empty(mutex->wait_queue)); //IDAN BORIS: might be not empty.
		mutex->owner_uthread_id = uthread_self();
//		write(1,"%\n",2);
        if(!thread_list_insert_head(all_threads[uthread_self()]->locked_mutexes, mutex)){
                //inserts this mutex into the list of mutexes we are currently holding
                unlock_alarm();
                return MUTEX_FAILURE;
        }
//        write(1,"^\n",2);
        mutex->lock_count++; //=1
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
//	write(1,"&\n",2);
	uthread_id* self_id = (uthread_id*)malloc(sizeof(uthread_id)); //allocating so that we can put this in the list
	if (!self_id){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	*self_id = uthread_self();
	if (mutex->owner_uthread_id == *self_id){
		free(self_id);
        mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	if (!thread_list_insert_tail(mutex->wait_queue, self_id)){ //this inserts at the end of the list
		free(self_id);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	all_threads[*self_id]->mutex_waiting_on = mutex; //remembering that we're waiting for this mutex
	deactivate_thread_and_cs(all_threads[*self_id]);
	unlock_alarm();
	return MUTEX_SUCCESS; //this line will run when the mutex is finally ours
}

int uthread_mutex_try_lock(uthread_mutex_t mutex) {
	if (!mutex)
		return MUTEX_INVALID;
	lock_alarm();
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (mutex->owner_uthread_id == uthread_self()){
		assert(mutex->lock_count > 0);
		mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	if (is_mutex_locked(mutex)){
		unlock_alarm();
		return MUTEX_LOCKED;
	}
	assert(thread_list_is_empty(mutex->wait_queue));
	mutex->owner_uthread_id = uthread_self();
    if(!thread_list_insert_head(all_threads[uthread_self()]->locked_mutexes, mutex)){
            //inserts this mutex into the list of mutexes we are currently holding
            unlock_alarm();
            return MUTEX_FAILURE;
    }
    assert(mutex->lock_count == 0);
    mutex->lock_count++;
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_unlock(uthread_mutex_t mutex) {
	if (!mutex)
		return MUTEX_INVALID;
	lock_alarm();
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (!is_mutex_locked(mutex)){
		unlock_alarm();
		return MUTEX_UNLOCKED;
	}
	if (mutex->owner_uthread_id != uthread_self()){ //check if we are the owners of this mutex
		unlock_alarm();
		return MUTEX_LOCKED;
	}
	thread_list* locked_mutexes = all_threads[uthread_self()]->locked_mutexes;
    node* n = thread_list_find(locked_mutexes,mutex);
    if (!n) {
		unlock_alarm();
		return MUTEX_FAILURE;
    }
    mutex->lock_count--;
    if (mutex->lock_count==0) {
		if(!thread_list_remove(locked_mutexes, mutex)){ //remove this mutex from the list of mutexes we hold
			mutex->lock_count++;
			unlock_alarm();
			return MUTEX_FAILURE;
		}
		if (thread_list_is_empty(mutex->wait_queue)){
			mutex->owner_uthread_id = -1;
			unlock_alarm();
			return MUTEX_UNLOCKED;
		}
		mutex->lock_count++;
		uthread_id new_owner_uthread_id;
		if (!(new_owner_uthread_id = *(int*)(thread_list_remove_head(mutex->wait_queue)))){
			unlock_alarm();
			return MUTEX_FAILURE;
		}
		mutex->owner_uthread_id = new_owner_uthread_id;

		if(!thread_list_insert_tail(all_threads[new_owner_uthread_id]->locked_mutexes, mutex)){
			//inserts this mutex into the list of mutexes the new thread is currently holding
			unlock_alarm();
			return MUTEX_FAILURE;
		}
		all_threads[new_owner_uthread_id]->mutex_waiting_on = NULL; //we're not waiting for this mutex anymore
		activate_thread(all_threads[new_owner_uthread_id]);
    }

	unlock_alarm();
	return MUTEX_SUCCESS;
}

void uthread_mutex_free_general()
{
	thread_list_destroy(mutex_list);
}

int uthread_free_mutexes(uthread_id id) {
	assert (id >= 0);
	node* n;
	uthread_mutex_t mutex;
	assert(all_threads[id]->locked_mutexes);
	thread_list_for_each(all_threads[id]->locked_mutexes, n) {
		mutex = (uthread_mutex_t) n->entry;
		assert(mutex->owner_uthread_id == uthread_self());
		if (thread_list_is_empty(mutex->wait_queue)){
			mutex->owner_uthread_id = -1;
			mutex->lock_count = 0;
		}
		else {
			uthread_id new_owner_thread_id;
			if (!(new_owner_thread_id = *((uthread_id*)thread_list_remove_head(mutex->wait_queue)))){
				return MUTEX_FAILURE;
			}
			if(!thread_list_insert_head(all_threads[new_owner_thread_id]->locked_mutexes, mutex)){
				//inserts this mutex into the list of mutexes the
				//new thread is currently holding
				return MUTEX_FAILURE;
			}
			all_threads[new_owner_thread_id]->mutex_waiting_on = NULL;
			//we are not waiting for this mutex anymore
			mutex->owner_uthread_id = new_owner_thread_id;
			mutex->lock_count = 1;
			activate_thread(all_threads[new_owner_thread_id]);
		}
	}

    if (all_threads[id]->mutex_waiting_on){
    	thread_list_for_each(all_threads[id]->mutex_waiting_on->wait_queue, n){
    		if (*(uthread_id*)(n->entry) == id){
    			break;
    		}
    	}
    	if (!n) {
    		return MUTEX_FAILURE;
    	}
    	if(!thread_list_remove_node(all_threads[id]->mutex_waiting_on->wait_queue, n)){
    		return MUTEX_FAILURE;
    	}
    }
    return MUTEX_SUCCESS;
}
